﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.DataAccess.SqlDatabase.Formatters;
using Fuse8.DomainFramework.Domain;

namespace Fuse8.DomainFramework.DataAccess.SqlDatabase
{
	internal class ExpressionToSqlTransformRulesService : IExpressionToSqlTransformRulesService
	{
		private static readonly Dictionary<ExpressionType, TemplateResult> _templates = new Dictionary<ExpressionType, TemplateResult>
		                                                         	{
																		{ExpressionType.Convert, new TemplateResult {Template = "{operand}"}},
																		{ExpressionType.OrElse, new TemplateResult {Template = "({left}) OR ({right})"}},
																		{ExpressionType.And, new TemplateResult { Template = "({left}) AND ({right})"}},
																		{ExpressionType.AndAlso, new TemplateResult { Template = "({left}) AND ({right})"}},
																		{ExpressionType.ExclusiveOr, new TemplateResult { Template = "(({left}) AND NOT ({right})) OR (({right}) AND NOT ({left}))"}},
																		{ExpressionType.Not, new TemplateResult { Template = "NOT ({operand})"}},
																		{ExpressionType.LessThan, new TemplateResult { Template = "{left} < {right}"}},
																		{ExpressionType.LessThanOrEqual, new TemplateResult { Template = "{left} <= {right}"}},
																		{ExpressionType.GreaterThan, new TemplateResult { Template = "{left} > {right}"}},
																		{ExpressionType.GreaterThanOrEqual, new TemplateResult { Template = "{left} >= {right}"}},
																		{ExpressionType.Equal, new TemplateResult { Template = "{left} = {right}", FormattingService = new EqualsFormattingService()}},
																		{ExpressionType.NotEqual, new TemplateResult { Template = "{left} != {right}", FormattingService = new NotEqualsFormattingService()}},
																		{ExpressionType.Negate, new TemplateResult { Template = "-({operand})"}},
																		{ExpressionType.Add, new TemplateResult { Template = "{left} + {right}"}},
																		{ExpressionType.Subtract, new TemplateResult { Template = "{left} - {right}"}},
																		{ExpressionType.Divide, new TemplateResult { Template = "({left}) / ({right})"}},
																		{ExpressionType.Multiply, new TemplateResult { Template = "({left}) * ({right})"}},
																		{ExpressionType.Power, new TemplateResult { Template = "POWER({left}, new TemplateResult { Template = {right})"}},
																		{ExpressionType.Modulo, new TemplateResult { Template = "({left}) % ({right})"}},
																		{ExpressionType.Constant, new TemplateResult { Template = "{value}", FormattingService = new ConstantFormattingService()}},
																		{ExpressionType.NewArrayInit, new TemplateResult {Template = "({})"}}
		                                                         	};
		                                                         	
		private static readonly List<Func<MethodInfo, TemplateResult>> _methodTemplates = new List<Func<MethodInfo, TemplateResult>>
                                                                      	{
                                                                      		p => p.Name == "Equals" ? new TemplateResult {Template = "{object} = {parameters[0]}" } : null,
                                                                      		MethodInfoGenerator<DateTime>("AddDays", "DATEADD (DAY, {parameters[0]}, {object})", null),
                                                                      		MethodInfoGenerator<DateTime>("AddMonth", "DATEADD (MONTH, {parameters[0]}, {object})", null),
                                                                      		MethodInfoGenerator<DateTime>("AddYear", "DATEADD (YEAR, {parameters[0]}, {object})", null),
                                                                      		MethodInfoGenerator<string>("Contains", "{object} LIKE '%' + {parameters[0]} + '%' ESCAPE '\\'", LikeFormattingService.Instance),
                                                                      		MethodInfoGenerator<string>("StartsWith", "{object} LIKE {parameters[0]} + '%' ESCAPE '\\'", LikeFormattingService.Instance),
                                                                      		MethodInfoGenerator<string>("EndsWith", "{object} LIKE '%' + {parameters[0]} ESCAPE '\\'", LikeFormattingService.Instance),
																			p => typeof(IEnumerable).IsAssignableFrom(p.DeclaringType) && p.Name == "Contains" ? new TemplateResult {Template = "{parameters[0]} IN ({object})"}: null,
																			p => typeof(Enumerable).IsAssignableFrom(p.DeclaringType) && p.Name == "Contains" ? new TemplateResult {Template = "{parameters[1]} IN ({parameters[0]})"}: null
                                                                      	};

		private static readonly List<Func<MemberInfo, string>> _memberTemplates = new List<Func<MemberInfo, string>>
                                                                      	{
                                                                      		p => p.DeclaringType.IsNullable() && p.Name == "HasValue" ? "{object} IS NOT NULL" : null,
                                                                      		p => p.DeclaringType.IsNullable() && p.Name == "Value" ? "{object}" : null,
                                                                      		p => p.DeclaringType.IsAssignableFrom(typeof(string)) && p.Name == "Length" ? "LEN({object})" : null
                                                                      	};

		private static readonly List<Func<Expression, IFormattingService>> _expressionFormatters = new List<Func<Expression, IFormattingService>>
		                                                                                           	{
		                                                                                           		p => p.NodeType == ExpressionType.MemberAccess && !(((MemberExpression)p).Member.DeclaringType.IsNullable() && ((MemberExpression)p).Member.Name == "HasValue") && p.Type == typeof(bool) || p.Type == typeof(bool?) ? new BooleanMemberExpressionFormattingService() : null,
		                                                                                           		p => p.Type == typeof(bool) || p.Type == typeof(bool?) ? new BooleanExpressionFormattingService() : null
		                                                                                           	};

		public TemplateResult GetExpressionTemplate(Expression expression)
		{
			return _templates.ContainsKey(expression.NodeType) ? _templates[expression.NodeType] : null;
		}

		public TemplateResult GetMethodTemplate(MethodInfo methodInfo)
		{
			return _methodTemplates.Select(p => p(methodInfo)).FirstOrDefault(p => p != null);
		}

		public string GetMemberTemplate(MemberInfo memberInfo)
		{
			return _memberTemplates.Select(p => p(memberInfo)).FirstOrDefault(p => !string.IsNullOrEmpty(p));
		}

		public IFormattingService GetExpressionFormatter(Expression expression)
		{
			return _expressionFormatters.Select(p => p(expression)).FirstOrDefault(p => p != null);
		}

		private static Func<MethodInfo, TemplateResult> MethodInfoGenerator<T>(string name, string template, IFormattingService formattingService)
		{
			return p => p.DeclaringType == typeof (T) && p.Name == name ? new TemplateResult {Template = template, FormattingService = formattingService} : null;
		}
	}
}